#pragma once

#include "Cloth_Lib.h"
#include "Points.h"
class Mesh
{
public:
	int num_particles_width; // number of particles in "width" direction
	int num_particles_height; // number of particles in "height" direction
	int width;
	int height;
	Vec3 normal;
	// total number of particles is num_particles_width*num_particles_height

	std::vector<Particle> particles; // all particles that are part of this cloth
	std::vector<Constraint> constraints; // alle constraints between particles as part of this cloth

	Particle* getParticle(int x, int y) {return &particles[y*num_particles_width + x];}
	void makeConstraint(Particle *p1, Particle *p2) {constraints.push_back(Constraint(p1,p2));}
	float scale;
	Lines lines;
	bool valued_point[100][100];
	Points point_list[100];
	void Mesh::drawTriangle(Particle *p1, Particle *p2, Particle *p3, const Vec3 color);
public:
	Mesh(void);
	Mesh(char* name,float depth) ;
	~Mesh(void);
	void LoadData(char* filename);
	void SaveData(char *filename);
	void drawShaded();
	void Mesh::timeStep();
	void Mesh::addForce(const Vec3 direction);
	void Mesh::windForce(const Vec3 direction);
	void Mesh::ballCollision(const Vec3 center,const float radius );
	void Mesh::addWindForcesForTriangle(Particle *p1,Particle *p2,Particle *p3, const Vec3 direction);
	Vec3 Mesh::calcTriangleNormal(Particle *p1,Particle *p2,Particle *p3);
	
};
Mesh::Mesh(char* name,float depth=0) 
{
	LoadData(name);
	width/=10;
	height/=10;
	particles.resize(num_particles_width*num_particles_height); //I am essentially using this vector as an array with room for num_particles_width*num_particles_height particles

		// creating particles in a grid of particles from (0,0,0) to (width,-height,0)
		particles.resize(num_particles_width*num_particles_height); //I am essentially using this vector as an array with room for num_particles_width*num_particles_height particles

		// creating particles in a grid of particles from (0,0,0) to (width,-height,0)
		for(int x=0; x<num_particles_width; x++)
		{
			for(int y=0; y<num_particles_height; y++)
			{
				if(valued_point[x][y])
				{
					Vec3 pos = Vec3(width * (x/(float)num_particles_width),-height * (y/(float)num_particles_height),depth);
					particles[y*num_particles_width+x]= Particle(pos); // insert particle in column x at y'th row
				}
			}
		}

		// Connecting immediate neighbor particles with constraints (distance 1 and sqrt(2) in the grid)
		for(int x=0; x<num_particles_width; x++)
		{
			for(int y=0; y<num_particles_height; y++)
			{
				if(valued_point[x][y])
				{
					if(valued_point[x+1][y])
					if (x<num_particles_width-1) makeConstraint(getParticle(x,y),getParticle(x+1,y));

					if(valued_point[x][y+1])
					if (y<num_particles_height-1) makeConstraint(getParticle(x,y),getParticle(x,y+1));

					if(valued_point[x+1][y+1])
					if (x<num_particles_width-1 && y<num_particles_height-1) makeConstraint(getParticle(x,y),getParticle(x+1,y+1));

					if(valued_point[x][y+1])
					if(valued_point[x+1][y])
					if (x<num_particles_width-1 && y<num_particles_height-1) makeConstraint(getParticle(x+1,y),getParticle(x,y+1));
				}
			}
		}


		// Connecting secondary neighbors with constraints (distance 2 and sqrt(4) in the grid)
		/*for(int x=0; x<num_particles_width; x++)
		{
			for(int y=0; y<num_particles_height; y++)
			{
				if (x<num_particles_width-2) makeConstraint(getParticle(x,y),getParticle(x+2,y));
				if (y<num_particles_height-2) makeConstraint(getParticle(x,y),getParticle(x,y+2));
				if (x<num_particles_width-2 && y<num_particles_height-2) makeConstraint(getParticle(x,y),getParticle(x+2,y+2));
				if (x<num_particles_width-2 && y<num_particles_height-2) makeConstraint(getParticle(x+2,y),getParticle(x,y+2));			}
		}*/


		// making the upper left most three and right most three particles unmovable
		//for(int i=0;i<3; i++)
		//{
		//	getParticle(0+i ,0)->offsetPos(Vec3(0.5,0.0,0.0)); // moving the particle a bit towards the center, to make it hang more natural - because I like it ;)
		//	getParticle(0+i ,0)->makeUnmovable(); 

		//	getParticle(0+i ,0)->offsetPos(Vec3(-0.5,0.0,0.0)); // moving the particle a bit towards the center, to make it hang more natural - because I like it ;)
		//	getParticle(num_particles_width-1-i ,0)->makeUnmovable();
		//}


		for(int i=0;i<num_particles_width; i++)
		{
			for(int j=0;j<20;j++)
			{
				if(valued_point[i][j])
				{
					//getParticle(0+i ,0)->offsetPos(Vec3(0.5,0.0,0.0)); // moving the particle a bit towards the center, to make it hang more natural - because I like it ;)
					//getParticle(i,j)->offsetPos(Vec3(0.5,0.0,0.0));
					getParticle(i ,j)->pos.f[2] = 0;
					getParticle(i ,j)->makeUnmovable(); 
					break;
				}
			}
			
			
		}
}
void Mesh::SaveData(char *filename)
{
	
	ofstream f;

	f.open(filename,ios::out);
	f << num_particles_width << " " << num_particles_height << endl;
	f << width << " " << height << endl;
	for(int i =0;i<num_particles_height;i++)
	{
		for(int j =0;j<num_particles_width;j++)
		{
			f << valued_point[j][i] << " ";
		}
		f << endl;
	}
	f.close();
	lines.Save(filename);
	f.open(filename,ios::app);
	for(int i =0;i<lines.n;i++)
	{
		f << point_list[i].n << " ";
		for(int j=0;j<100;j++)
			if(point_list[i].p[j].x != -1)
				f << point_list[i].p[j].x << " " << point_list[i].p[j].y << " ";
		f << endl;
	}
	f.close();
}
void Mesh::LoadData(char* filename)
{
	fstream f;
	f.open(filename,ios::in);

	f >> num_particles_width;
	f >> num_particles_height;
	f >> width;
	f >> height;
	int c;
	for(int i =0;i<num_particles_height;i++)
	{
		for(int j =0;j<num_particles_width;j++)
		{
			f >> c;
			if(c ==1)
				valued_point[j][i] = true;
			else
				valued_point[j][i] = false;
		}
		
	}
	f>>lines.n;
	for(int i =0;i<lines.n;i++)
	{
		f>> lines.start[i].x;
		f>> lines.start[i].y;
		f>> lines.end[i].x;
		f>> lines.end[i].y;
	}
	for(int i =0;i<lines.n;i++)
	{
		f >>point_list[i].n;
		for(int j =0;j<point_list[i].n;j++)
		{
			f >> point_list[i].p[j].x;
			f >> point_list[i].p[j].y;

			//cout << point_list[i].p[j].x << " ";
			//cout << point_list[i].p[j].y << endl;
		}
	}

	
	f.close();
}

Mesh::Mesh(void)
{

}

Mesh::~Mesh(void)
{
}
void Mesh::drawShaded()
{
	// reset normals (which where written to last frame)
	std::vector<Particle>::iterator particle;
	for(particle = particles.begin(); particle != particles.end(); particle++)
	{
		(*particle).resetNormal();
	}

	//create smooth per particle normals by adding up all the (hard) triangle normals that each particle is part of
	for(int x = 0; x<num_particles_width-1; x++)
	{
		for(int y=0; y<num_particles_height-1; y++)
		{
			if(valued_point[x][y])
					if(valued_point[x+1][y])
						if(valued_point[x][y+1])
							if(valued_point[x+1][y+1])
								{
								Vec3 normal = calcTriangleNormal(getParticle(x+1,y),getParticle(x,y),getParticle(x,y+1));
								getParticle(x+1,y)->addToNormal(normal);
								getParticle(x,y)->addToNormal(normal);
								getParticle(x,y+1)->addToNormal(normal);

								normal = calcTriangleNormal(getParticle(x+1,y+1),getParticle(x+1,y),getParticle(x,y+1));
								getParticle(x+1,y+1)->addToNormal(normal);
								getParticle(x+1,y)->addToNormal(normal);
								getParticle(x,y+1)->addToNormal(normal);
								}
		}
	}

	glBegin(GL_TRIANGLES);
	for(int x = 0; x<num_particles_width-1; x++)
		{
			for(int y=0; y<num_particles_height-1; y++)
			{

				Vec3 color(0,0,0);
				if (x%2) // red and white color is interleaved according to which column number
					color = Vec3(0.6f,0.2f,0.2f);
				else
					color = Vec3(1.0f,1.0f,1.0f);
				if(valued_point[x][y])
				{
					if(valued_point[x+1][y] && valued_point[x][y+1])
					{
						drawTriangle(getParticle(x,y+1),getParticle(x,y),getParticle(x+1,y),color);
						if(valued_point[x+1][y+1])
							drawTriangle(getParticle(x,y+1),getParticle(x+1,y),getParticle(x+1,y+1),color);
					}
					else
					if(valued_point[x+1][y] && valued_point[x+1][y+1])
						drawTriangle(getParticle(x,y),getParticle(x+1,y),getParticle(x+1,y+1),color);
					else
					if(valued_point[x][y+1] && valued_point[x+1][y+1])
						drawTriangle(getParticle(x,y+1),getParticle(x,y),getParticle(x+1,y+1),color);
				}
				else
				if(valued_point[x+1][y] && valued_point[x][y+1]&&valued_point[x+1][y+1])
					drawTriangle(getParticle(x,y+1),getParticle(x+1,y),getParticle(x+1,y+1),color);
			}
		}
	glEnd();
}
void Mesh::timeStep()
{
	std::vector<Constraint>::iterator constraint;
	for(int i=0; i<CONSTRAINT_ITERATIONS; i++) // iterate over all constraints several times
	{
		for(constraint = constraints.begin(); constraint != constraints.end(); constraint++ )
		{
			(*constraint).satisfyConstraint(); // satisfy constraint.
		}
	}

	std::vector<Particle>::iterator particle;
	for(particle = particles.begin(); particle != particles.end(); particle++)
	{
		(*particle).timeStep(); // calculate the position of each particle at the next time step.
	}
}

/* used to add gravity (or any other arbitrary vector) to all particles*/
void Mesh::addForce(const Vec3 direction)
{
	std::vector<Particle>::iterator particle;
	for(particle = particles.begin(); particle != particles.end(); particle++)
	{
		(*particle).addForce(direction); // add the forces to each particle
	}

}

/* used to add wind forces to all particles, is added for each triangle since the final force is proportional to the triangle area as seen from the wind direction*/
void Mesh::windForce(const Vec3 direction)
{
	for(int x = 0; x<num_particles_width-1; x++)
	{
		for(int y=0; y<num_particles_height-1; y++)
		{
			if(valued_point[x][y])
				if(valued_point[x+1][y])
					if(valued_point[x][y+1])
						if(valued_point[x+1][y+1])
				{
				addWindForcesForTriangle(getParticle(x+1,y),getParticle(x,y),getParticle(x,y+1),direction);
				addWindForcesForTriangle(getParticle(x+1,y+1),getParticle(x+1,y),getParticle(x,y+1),direction);
				}
		}
	}
}

/* used to detect and resolve the collision of the cloth with the ball.
This is based on a very simples scheme where the position of each particle is simply compared to the sphere and corrected.
This also means that the sphere can "slip through" if the ball is small enough compared to the distance in the grid bewteen particles
*/
void Mesh::ballCollision(const Vec3 center,const float radius )
{
	std::vector<Particle>::iterator particle;
	for(particle = particles.begin(); particle != particles.end(); particle++)
	{
		Vec3 v = (*particle).getPos()-center;
		float l = v.length();
		if ( v.length() < radius) // if the particle is inside the ball
		{
			(*particle).offsetPos(v.normalized()*(radius-l)); // project the particle to the surface of the ball
		}
	}
}
void Mesh::addWindForcesForTriangle(Particle *p1,Particle *p2,Particle *p3, const Vec3 direction)
{
	Vec3 normal = calcTriangleNormal(p1,p2,p3);
	Vec3 d = normal.normalized();
	Vec3 force = normal*(d.dot(direction));
	p1->addForce(force);
	p2->addForce(force);
	p3->addForce(force);
}
Vec3 Mesh::calcTriangleNormal(Particle *p1,Particle *p2,Particle *p3)
{
	Vec3 pos1 = p1->getPos();
	Vec3 pos2 = p2->getPos();
	Vec3 pos3 = p3->getPos();

	Vec3 v1 = pos2-pos1;
	Vec3 v2 = pos3-pos1;

	return v1.cross(v2);
}
void Mesh::drawTriangle(Particle *p1, Particle *p2, Particle *p3, const Vec3 color)
{
	glColor3fv( (GLfloat*) &color );

	glNormal3fv((GLfloat *) &(p1->getNormal().normalized() ));
	glVertex3fv((GLfloat *) &(p1->getPos() ));

	glNormal3fv((GLfloat *) &(p2->getNormal().normalized() ));
	glVertex3fv((GLfloat *) &(p2->getPos() ));

	glNormal3fv((GLfloat *) &(p3->getNormal().normalized() ));
	glVertex3fv((GLfloat *) &(p3->getPos() ));
}